/**
 * Copyright 2010 Nicholas Alexander Cross
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.dataporter.data;

import org.apache.log4j.Logger;
import org.dataporter.io.DataReader;
import org.dataporter.io.DataWriter;
import org.dataporter.processing.*;
import org.dataporter.validation.DataValidator;

import java.util.*;

public class ImporterBuilder<Source, Sink> {

    private static final Logger logger = Logger.getLogger( ImporterBuilder.class );

    private DataReader<Source> reader;
    private DataWriter<Sink> writer;

    private Map<String, List<DataProcessor>> processors = new HashMap<String, List<DataProcessor>>();
    private Map<String, List<DataValidator>> validators = new HashMap<String, List<DataValidator>>();

    public ImporterBuilder( DataReader<Source> reader, DataWriter<Sink> writer ) {
        this.reader = reader;
        this.writer = writer;
    }

    public Importer<Source, Sink> newImporter() {
        Importer<Source, Sink> importer = new Importer<Source, Sink>( reader, writer );

        Set<String> entitiesForProcessing = processors.keySet();
        Set<String> entitiesForValidation = validators.keySet();

        for ( String entityName : entitiesForProcessing )
        {
            List<DataProcessor> entityProcessors = processors.get( entityName );

            for ( DataProcessor processor : entityProcessors )
            {
                importer.addProcessor( entityName, processor );
            }
        }

        for ( String entityName : entitiesForValidation )
        {
            List<DataValidator> entityValidators = validators.get( entityName );

            for ( DataValidator validator : entityValidators )
            {
                importer.addValidator( entityName, validator );
            }
        }

        return importer;
    }

    public EntityBuilder newEntityBuilder( String entityName ) {
        return new EntityBuilder( this, entityName );
    }

    public void addBoolean( String entityName, String fieldName ) {
        addProcessors( entityName, new BooleanValue( fieldName ) );
    }

    public void addDouble( String entityName, String fieldName ) {
        addProcessors( entityName, new DoubleValue( fieldName ) );
    }

    public void addFloat( String entityName, String fieldName ) {
        addProcessors( entityName, new FloatValue( fieldName ) );
    }

    public void addInteger( String entityName, String fieldName ) {
        addProcessors( entityName, new IntegerValue( fieldName ) );
    }

    public void addLong( String entityName, String fieldName ) {
        addProcessors( entityName, new LongValue( fieldName ) );
    }

    public void addShort( String entityName, String fieldName ) {
        addProcessors( entityName, new ShortValue( fieldName ) );
    }

    public void mapFields( String entityName, Map<String, String> mappings ) {
        addProcessors( entityName, new FieldMapper( mappings ) );
    }

    /**
     * Make sure you expose fields only after you map fields
     *
     * @param entityName
     * @param outputEntityName
     * @param fieldNames
     */
    public void exposeFields( String entityName, String outputEntityName, List<String> fieldNames ) {
        if ( fieldNames.size() > 0 )
        {
            HashMap<String, List<String>> fieldsToExpose = new HashMap<String, List<String>>();
            fieldsToExpose.put( outputEntityName, fieldNames );
            addProcessors( entityName, new FieldExposer( fieldsToExpose ) );
        }
        else
        {
            logger.warn( "Cannot expose no fields for entity: " + entityName );
        }
    }

    /**
     * Make sure you expose fields only after you map fields
     *
     * @param entityName
     * @param outputEntityName
     * @param fieldNames
     */
    public void exposeFields( String entityName, String outputEntityName, String... fieldNames ) {
        List<String> fields = new ArrayList<String>();
        fields.addAll( Arrays.asList( fieldNames ) );
        exposeFields( entityName, outputEntityName, fields );
    }

    public void addProcessors( String entityName, DataProcessor... processors ) {
        List<DataProcessor> entityProcessors = this.processors.get( entityName );

        if ( entityProcessors == null )
        {
            entityProcessors = new ArrayList<DataProcessor>();
            this.processors.put( entityName, entityProcessors );
        }

        entityProcessors.addAll( Arrays.asList( processors ) );
    }

    public void addValidators( String entityName, DataValidator... validators ) {
        List<DataValidator> entityValidators = this.validators.get( entityName );

        if ( entityValidators == null )
        {
            entityValidators = new ArrayList<DataValidator>();
            this.validators.put( entityName, entityValidators );
        }

        entityValidators.addAll( Arrays.asList( validators ) );
    }

    public static class EntityBuilder {

        private final ImporterBuilder builder;
        private final String entityName;

        private EntityBuilder( ImporterBuilder builder, String entityName ) {
            this.builder = builder;
            this.entityName = entityName;
        }

        public void addBoolean( String fieldName ) {
            builder.addBoolean( entityName, fieldName );
        }

        public void addDouble( String fieldName ) {
            builder.addDouble( entityName, fieldName );
        }

        public void addFloat( String fieldName ) {
            builder.addFloat( entityName, fieldName );
        }

        public void addInteger( String fieldName ) {
            builder.addInteger( entityName, fieldName );
        }

        public void addLong( String fieldName ) {
            builder.addLong( entityName, fieldName );
        }

        public void addShort( String fieldName ) {
            builder.addShort( entityName, fieldName );
        }

        public void addProcessors( DataProcessor... processors ) {
            builder.addProcessors( entityName, processors );
        }

        public void addValidators( DataValidator... validators ) {
            builder.addValidators( entityName, validators );
        }

        public FieldNameMapper mapField( String fieldName ) {
            return new FieldNameMapper( this, fieldName );
        }

        public void mapFields( Map<String, String> fieldNameMappings ) {
            builder.addProcessors( entityName, new FieldMapper( fieldNameMappings ) );
        }

        public void exposeFields( String outputEntityName, List<String> fieldNames ) {
            builder.exposeFields( entityName, outputEntityName, fieldNames );
        }

        public void exposeFields( String outputEntityName, String... fieldNames ) {
            builder.exposeFields( entityName, outputEntityName, fieldNames );
        }

        public static class FieldNameMapper {

            private final EntityBuilder builder;
            private final String fieldName;

            private FieldNameMapper( EntityBuilder builder, String fieldName ) {
                this.builder = builder;
                this.fieldName = fieldName;
            }

            public void to( String mappingName ) {
                Map<String, String> mappings = new HashMap<String, String>();
                mappings.put( fieldName, mappingName );
                builder.mapFields( mappings );
            }
        }

    }

}
